using System;
using System.Collections.Generic;
using System.Text;

namespace Calc.Parser
{
	public class Lexer
	{
		protected string _data;
		protected int _index;
		protected int _lastIndex;

		private readonly char[] _operators =  { '+', '-', '*', '/', '%', '=', '!' };

		protected enum LexerState
		{
			Begin,
			IntegerConstant,
			FloatingConstant,
			String,
		}

		public Lexer(string data)
		{
			_data = data;
			_index = 0;
			_lastIndex = 0;
		}

		/// <summary>
		/// Returns the next token in the string without moving forward
		/// </summary>
		/// <returns></returns>
		public LexerToken PeekNextToken()
		{
			int index = _index;
			int lindex = _lastIndex;

			LexerToken ret = GetNextToken();

			_index = index;
			_lastIndex = lindex;

			return ret;
		}

		/// <summary>
		/// Returns the next token in the string
		/// </summary>
		/// <returns></returns>
		public LexerToken GetNextToken()
		{
			if ( _index >= _data.Length )
				return new LexerToken(null, LexerTokenType.End, _index);

			LexerState state = LexerState.Begin;

			string token = "";

			_lastIndex = _index;

			for ( ; _index < _data.Length; _index++ )
			{
				char c = GetNextChar();

				bool isDigit = char.IsDigit(c);
				bool isChar = char.IsLetter(c);
				bool isRParen = (c == ')');
				bool isLParen = (c == '(');
				bool isPeriod = (c == '.');
				bool isComma = (c == ',');
				bool isWS = char.IsWhiteSpace(c);


				switch ( state )
				{
					case LexerState.Begin:
						if ( isDigit )
						{
							state = LexerState.IntegerConstant;
							token += c;
							continue;
						}

						if ( isChar )
						{
							state = LexerState.String;
							token += c;
							continue;
						}

						if ( isRParen )
						{
							_index++;
							return new LexerToken(null, LexerTokenType.RightParenthesis, _lastIndex);
						}

						if ( isLParen )
						{
							_index++;
							return new LexerToken(null, LexerTokenType.LeftParenthesis, _lastIndex);
						}

						if ( isComma )
						{
							_index++;
							return new LexerToken(null, LexerTokenType.Comma, _lastIndex);
						}

						if ( isWS )
						{
							_lastIndex++;
							continue;
						}
						if ( IsOperator(c) )
						{
							char nc = PeekNextChar();

							if ( IsOperator(nc) )
							{
								_index++;
								string op = c.ToString();
								op += nc.ToString();
								return new LexerToken(op, LexerTokenType.Operator, _lastIndex);
							}
							else
							{
								_index++;
								return new LexerToken(c, LexerTokenType.Operator, _lastIndex);
							}
						}

						// this is an error state
						return GetErrorToken(c);
					case LexerState.IntegerConstant:
						if ( isDigit )
						{
							token += c;
							continue;
						}

						if ( isPeriod )
						{
							state = LexerState.FloatingConstant;
							token += c;
							continue;
						}

						if ( isWS )
							_index++;

						if ( isWS || IsOperator(c) || isRParen || isLParen || isComma )
						{
							return new LexerToken(token, LexerTokenType.Constant, _lastIndex);
						}

						// this is an error state
						return GetErrorToken(c);
					case LexerState.FloatingConstant:
						if ( isDigit )
						{
							token += c;
							continue;
						}

						if ( isWS )
							_index++;

						if ( isWS || IsOperator(c) || isRParen || isLParen || isComma )
						{
							return new LexerToken(token, LexerTokenType.Constant, _lastIndex);
						}

						// this is an error state
						return GetErrorToken(c);
					case LexerState.String:
						if ( isDigit || isChar )
						{
							token += c;
							continue;
						}

						if ( isWS )
							_index++;

						if ( isLParen || isRParen || IsOperator(c) || isWS || isComma )
						{
							return new LexerToken(token, LexerTokenType.String, _lastIndex);
						}

						return GetErrorToken(c);
				}
			}

			switch ( state )
			{
				case LexerState.Begin:
					return new LexerToken(null, LexerTokenType.End, _lastIndex);
				case LexerState.FloatingConstant:
				case LexerState.IntegerConstant:
					return new LexerToken(token, LexerTokenType.Constant, _lastIndex);
				case LexerState.String:
					return new LexerToken(token, LexerTokenType.String, _lastIndex);
			}

			return new LexerToken(null, LexerTokenType.End, _index);
		}

		private char GetNextChar()
		{
			return _data[_index];
		}

		private char PeekNextChar()
		{
			if ( _index + 1 < _data.Length )
				return _data[_index + 1];

			return (char)0;
		}

		protected LexerToken GetErrorToken(char c)
		{
			return new LexerToken(new string(c, 1), _index);
		}

		protected bool IsOperator(char c)
		{
			for ( int i = 0; i < _operators.Length; i++ )
			{
				if ( _operators[i] == c )
					return true;
			}

			return false;
		}
	}
}
